home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Shareware Grab Bag
/
Shareware Grab Bag.iso
/
090
/
toolfix.arc
/
FLUSH.ACC
< prev
next >
Wrap
Text File
|
1987-03-03
|
14KB
|
300 lines
*******************************************************************************
FLUSH.ACC
Version 1.3
February 10, 1986
by Randy Forgaard
CompuServe 70307,521
This file, FLUSH.ACC, is related to the file FLUSH.PAS in DL 1 of the Borland
SIG on CompuServe, but FLUSH.PAS is not needed in order to use the routines
below.
This file is for use with the Turbo Access portion of the MS-DOS Turbo
(Database) Toolbox Version 1.2, together with MS-DOS or PC-DOS Turbo Pascal
3.01A or higher (regular, 8087, or BCD version). If you have Version 1.00,
1.01, or 1.1 of the Toolbox, see the file TBXFIX in DL 1 of the Borland SIG for
the source code changes that will upgrade your copy to Version 1.2 (plus
additional fixes). If you have Version 3.00A or 3.00B of Turbo Pascal, arrange
with Borland Customer Service to upgrade your copy of Turbo to 3.01A or higher
(3.00A and 3.00B are not compatible with Turbo Database Toolbox). The routines
below will not work with earlier (pre-3.0) versions of Turbo Pascal, nor with
Turbo Pascal for operating systems other than DOS.
This file describes certain changes that you can make to the source code of
Turbo Access to make your database virtually impervious to system crashes.
Normally, when you use Turbo Access and you use MakeFile, AddRec, PutRec,
DeleteRec, MakeIndex, AddKey, or DeleteKey to create or change a DataFile or
IndexFile, not all of the changes you make are necessarily written to the disk
right away. Typically, the most recent changes are kept in memory buffers by
Turbo Access, the Turbo run-time system, and/or DOS. This technique allows
file output to proceed more quickly, since an entire index page or disk sector
can be written to the disk at once. When a file gets closed, its memory
buffers are all written to disk. However, if the system should crash (due to
program error, accidental Ctrl-Alt-Del, power failure, tripping over the plug,
etc.) after a file has been changed and before it gets closed, the recent
changes that are in memory but not on the disk will not get written. Even more
insidious, a system crash can mean that the file length in the directory entry,
or the record numbers in IndexFiles, do not get written to disk, rendering the
information in the database inconsistent and probably unusable.
One can guard against system crashes by flushing all information in a file's
memory buffers out to disk, every time that the contents of that file changes.
Unfortunately, using the routines provided by Turbo Access, a DataFile or
IndexFile can only be flushed by closing and reopening the file. This is
primarily because DOS provides no explicitly documented way to flush a file's
buffers without closing/reopening. Closing and reopening a file every time its
contents change is extraordinarily expensive at run-time, largely because DOS
must perform a directory lookup every time a file is reopened.
Fortunately, there actually IS a little-known method for flushing a file's
buffers and updating the file length in the file's directory entry under DOS,
without reopening the file. The technique for "flushing" a file is: 1) invoke
DOS function 45H, "Duplicate a File Handle (DUP)," to duplicate the file
handle, and then 2) invoke DOS function 3EH, "Close a File Handle" to close the
extra file handle that you just created (this action does not close the
original file handle). The "Close" function 3EH flushes the file's buffers, as
documented in the DOS Technical Reference manual. Yet the original file handle
is still valid and usable, so the file does not need to be reopened. Thanks
for this clever technique go to Dan Daetwyler in a letter to Ray Duncan's
16-Bit Software Toolbox column in the December '85 issue of Dr. Dobb's Journal.
One of the routines below is a new routine for use with Turbo Access, called
FlushFile. This procedure will perform an actual flush on any DataFile, so
that the DataFile will be completely up-to-date and consistent even if the
system should crash after the flush. The FlushFile routine uses the technique
described above. Note: The FLUSH.PAS file, mentioned above, contains two
routines for flushing standard Turbo files (rather than Turbo Access files).
The FlushAny procedure in FLUSH.PAS is very similar to the FlushFile procedure
below. The differences are that FlushFile updates the internal Record 0
maintained by Turbo Access, and that FlushFile uses TaIOcheck, the internal
Turbo Access routine, to report errors, rather than writing the errors directly
to the primary output.
Just as FlushFile will flush any DataFile, FlushIndex (the other procedure
below) will flush any IndexFile. The FlushIndex code is similar to the
CloseIndex routine included with Turbo Access, with the following differences:
the index pages are not removed from the page stack after they are written to
disk (which can make subsequent index searches proceed faster), and the final
call to CloseFile is replaced by a call to FlushFile.
After making the Turbo Access source code changes indicated below, FlushFile
and FlushIndex may be called explicitly by the calling program whenever
desired. Alternatively, the calling program may set the global Boolean
variable FailSafe to "true," and the (modified) Turbo Access routines will
automatically invoke FlushFile or FlushIndex (depending on context) whenever a
DataFile or IndexFile is changed. By default, FailSafe is "false." If you
decide to set FailSafe to "true," it should be done immediately after the call
to InitIndex at the beginning of your main program; i.e.:
...
InitIndex;
FailSafe := true;
...
With the changes below, the only possibility that data can become corrupted due
to a system crash is if the crash occurs between when the file gets changed and
when FlushFile or FlushIndex is automatically (or explicitly) invoked
immediately afterward. That window of vulnerability is a small number of
microseconds, and occurs only at the actual moment that the program outputs a
new data record or index key. If your program is reading records, searching
the database, waiting for user input, computing, printing, updating the screen,
etc., your database is still safe if the system crashes. However, if there
should be a power failure at the exact moment that data is being written, it is
likely that the failure will also cause serious problems on the disk as a
whole, trashing the FAT or some directories. Thus, in this very unlikely
event, the disk may need to be restored from backup tape or disks in any case,
and you probably shouldn't lose sleep over this possibility.
There are two important DISadvantages to using the Turbo Access code changes
below, assuming you have set FailSafe to "true":
1) MakeFile, AddRec, PutRec, DeleteRec, MakeIndex, AddKey, and DeleteKey
will all execute somewhat slower, because of the automatic file flush. This
delay will not be nearly as great as closing/reopening the file, but it can be
noticeable if done frequently. If your database application spends a
significant amount of time updating records, you might want to test how much
slower your application runs with FailSafe = "true" than with FailSafe =
"false." You can reduce the delay by living a little dangerously and running
with FailSafe = "false," invoking FlushFile and FlushIndex explicitly at
strategic points.
2) The file flushing code below only works with DOS. If you plan to port
your application to other operating systems later, you may want to build a more
robust scheme for detecting and recovering from system crashes, since you won't
be able take advantage of the crash protection below.
It would be wonderful if Turbo Access could be made to automatically flush file
buffers under other operating systems, in addition to DOS. If you find a way
to do this, PLEASE PLEASE add this information to the FLUSH.ACC file
accordingly, and re-upload to DL 1 of the Borland SIG! For that matter, if you
are feeling truly altruistic, you might want to make similar additions to
FLUSH.PAS, too, showing how to flush normal Turbo files under other operating
systems.
There are some other files in DL 1 of the Borland SIG that may be of interest.
These files describe how to modify the Database Toolbox to extend it for
various other purposes; e.g., EXTEND.ACC describes Toolbox modifications that
allow one to open up to 96 Toolbox files simultaneously (252 under DOS 3.0 and
higher) under DOS Turbo 3.0, and BIGTRE.BOX shows how to use more than 65535
records per DataFile under DOS. The modifications below have been tested with
EXTEND.ACC, so you may use the FLUSH.ACC and EXTEND.ACC modifications together.
However, the BIGTRE.BOX modifications have not yet been examined with respect
to FLUSH.ACC and EXTEND.ACC. Try a "BRO/KEY:TOOLBOX" and a "BRO/KEY:ACCESS" to
find these and other files in DL 1.
Note: If you are using both the FLUSH.ACC modifications and the EXTEND.ACC
modifications, you must apply the FLUSH.ACC modifications (below) first, THEN
the EXTEND.ACC modifications.
Many thinks to Rick Amerson (CompuServe 72477,1566) for his generous help in
testing the code below, to Andy Miller (CompuServe 70357,3656) for pointing out
that the routines below do not use any undocumented features of DOS function
calls, and to Peter Thomas (CompuServe 75716,2377) for suggesting the
efficiency improvement to FlushIndex.
Change Log:
Version 1.1: Removed caveats about undocumented use of DOS functions, since
this file actually only uses documented features of DOS.
Version 1.2: Slight changes to FlushIndex so that index pages do not get
removed from the page stack in memory when they get flushed to
disk. Can make subsequent index searches faster, since those
index pages will not need to be re-read from disk.
Version 1.3: Updated to reflect the changes in Version 1.2 of the Database
Toolbox, and the new version of TBXFIX.
*******************************************************************************
The code changes to add "flush" capability to MS-DOS Turbo Access Version 1.2
are shown below. Please BE SURE to make a backup copy of your Turbo Access
source code before making these changes. Note: When making the changes below,
be sure to use the version of ACCESS.BOX intended for use with Turbo 3.0. This
file might be called ACCESS.BOX or ACCESS3.BOX on your Toolbox disk. Check the
comment header at the beginning of the file to ensure you are using the correct
ACCESS.BOX.
STEP 1: Add the following declaration to ACCESS.BOX, immediately prior to
TaIOcheck:
const
FailSafe: Boolean = false;
STEP 2: Insert the following routine immediately after the routine TaIOcheck in
ACCESS.BOX:
{Flushes the buffers associated with the DataFile "DatF," and updates the file
length in the directory entry of "DatF," without closing "DatF."}
procedure FlushFile (var DatF: DataFile);
var
handle: Integer absolute DatF; {File handle is the first word of a DataFile}
regs: record
case Integer of
1: (AX, BX, CX, DX, BP, SI, DI, DS, ES, Flags: Integer);
2: (AL, AH, BL, BH, CL, CH, DL, DH: Byte)
end;
begin
{Code from beginning of CloseFile}
DatF.Int2 := DatF.NumRec;
Move(DatF.FirstFree, TaRecBuf, 8);
{Simulate a PutRec(DatF, 0, TaRecBuf) (to avoid FlushFile/PutRec recursion)}
Seek(DatF.F, 0);
IOstatus := IOresult;
TaIOcheck(DatF, 0);
BlockWrite(DatF.F, TaRecBuf, 1);
IOstatus := IOresult;
TaIOcheck(DatF, 0);
{Flush DatF}
IOstatus := $F0; {"Disk write error" I/O error...just in case}
regs.AH := $45; {DOS function to duplicate a file handle}
regs.BX := handle;
MsDos(regs);
if Odd(regs.Flags) then {Check if carry flag is set}
TaIOcheck(DatF, 0);
regs.BX := regs.AX; {Put new file handle into BX}
regs.AH := $3E; {Dos function to close a file handle}
MsDos(regs);
if Odd(regs.Flags) then {Check if carry flag is set}
TaIOcheck(DatF, 0)
end {FlushFile};
STEP 3: Insert the following routine immediately after the routine OpenIndex in
ACCESS.BOX:
{Flushes the buffers associated with the IndexFile "IdxF," and updates the file
length in the directory entry of "IdxF," without closing "IdxF."}
procedure FlushIndex (var IdxF: IndexFile);
var
I: Integer;
begin
{Similar to CloseIndex:}
for I := 1 to PageStackSize do
with TaPageStk[I] do
if (IndexFPtr = Addr(IdxF)) and Updated then
begin
TaPack(Page,IdxF.KeyL);
{Simulate a PutRec(IdxF.DataF, PageRef, Page) (to avoid redundant
FlushFile calls)}
Seek(IdxF.DataF.F, PageRef);
IOstatus := IOresult;
TaIOcheck(IdxF.DataF, PageRef);
BlockWrite(IdxF.DataF.F, Page, 1);
IOstatus := IOresult;
TaIOcheck(IdxF.DataF, PageRef);
TaUnpack(Page,IdxF.KeyL);
Updated := false
end;
IdxF.DataF.Int1 := IdxF.RR;
FlushFile(IdxF.DataF)
end {FlushIndex};
STEP 4: Also in ACCESS.BOX, add the following line just before the "end" at the
end of the PutRec routine:
if FailSafe then FlushFile(DatF);
STEP 5: Insert the following line just before the very last "end" statement at
the very end of the file ADDKEY.BOX:
if FailSafe then FlushIndex(IdxF);
STEP 6: Also, insert the above line just before the very last "end" statement
at the very end of the file DELKEY.BOX.
STEP 7: Don't forget to add the line "FailSafe := true;", if desired, to your
main program, immediately after the call to InitIndex.
That's it! If you have any questions, comments, or trouble with the above
changes, please feel free to contact me on the Borland SIG or via EasyPlex on
CompuServe. I sincerely hope the above information is useful to you.
-- Randy Forgaard